<?php
declare (strict_types=1);

namespace app\command;

use app\service\TcpServerEventHandleService;
use Exception;
use Swoole\Process;
use Swoole\Server;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;

class TcpServer extends Command
{
    protected $serv = null;
    protected $action = 'start';
    protected $host = '0.0.0.0';
    protected $port = 9605;

    protected $daemonize = false;
    protected $redis = null;

    protected function configure()
    {
        // 指令配置
        $this->setName('start_tcp_server')
            ->setDescription('TCP服务器')
            ->addArgument('action', Argument::REQUIRED, '[status|start|restart|reload|stop]')
            ->addOption('d', null, Option::VALUE_NONE, 'daemonize（守护进程）方式启动');
    }

    protected function execute(Input $input, Output $output)
    {
        try {
            $this->daemonize = (bool)$input->getOption('d');
            $this->action = $input->getArgument('action');

            $this->manager($output);
            $this->args($input);
            $this->init($input, $output);
        } catch (Exception $e) {
            $output->writeln('启动失败！' . $e->getMessage());
        }
    }

    protected function manager(Output $output)
    {
        if (!defined('SWOOLE_VERSION') || SWOOLE_VERSION <= '4.5') return false;
        $pidFilePath = runtime_path() . 'swoole_server.pid';
        if (file_exists($pidFilePath)) {
            $pid = (int)file_get_contents(runtime_path() . 'swoole_server.pid');
        }

        if (!empty($pid)) {
            if ('stop' == $this->action) {
                $status = Process::kill($pid);
                if ($status) {
                    $output->writeln("\033[38;5;1;4m[stop]进程已经退出\033[0m");
                    exit();
                }
            }
        } else {
            if ('stop' == $this->action) {
                $output->writeln('[error]进程尚未启动，无法执行停止操作！');
                exit();
            }
        }

        return true;
    }

    protected function args(Input $input)
    {
        global $argv;
        $argv[1] = $input->getArgument('action') ?? 'start';
        if ($input->hasOption('d')) {
            $argv[2] = '-d';
        } else {
            unset($argv[2]);
        }
    }

    protected function init(Input $input, Output $output)
    {
        try {
            $output->writeln('正在启动TCP服务...');
            $this->serv = new Server($this->host, $this->port);

            //设置参数
            //如果业务代码是全异步 IO 的，worker_num设置为 CPU 核数的 1-4 倍最合理
            //如果业务代码为同步 IO，worker_num需要根据请求响应时间和系统负载来调整，例如：100-500
            //假设每个进程占用 40M 内存，100 个进程就需要占用 4G 内存
            $this->serv->set([
                'reactor_num' => swoole_cpu_num(),
                'worker_num' => swoole_cpu_num(),          //设置启动的worker进程数。【默认值：CPU 核数】
                'max_request' => 10000,         //设置每个worker进程的最大任务数。【默认值：0 即不会退出进程】
                'daemonize' => $this->daemonize,       //守护进程化【默认值：0】
                'heartbeat_idle_time' => 60, // 表示一个连接如果600秒内未向服务器发送任何数据，此连接将被强制关闭
                'heartbeat_check_interval' => 30,  // 表示每60秒遍历一次

                'open_tcp_keepalive' => true,
                'tcp_keepidle' => 10, //4s没有数据传输就进行检测
                'tcp_keepinterval' => 3, //1s探测一次
                'tcp_keepcount' => 5, //探测的次数，超过5次后还没回包close此连接
                'pid_file' => runtime_path() . 'swoole_server.pid'
            ]);

            $tcpEventHandle = new TcpServerEventHandleService($input, $output);
            $this->serv->on('Start', [$tcpEventHandle, 'onStart']);

            $this->serv->on('WorkerStart', [$tcpEventHandle, 'onWorkerStart']);

            $this->serv->on('connect', [$tcpEventHandle, 'onConnect']);

            //监听数据接收事件
            $this->serv->on('receive', [$tcpEventHandle, 'onReceive']);

            //监听链接关闭事件
            $this->serv->on('close', [$tcpEventHandle, 'onClose']);

            //启动服务
            $this->serv->start();
            $output->writeln('服务启动完成!');
        } catch (Exception $e) {
            $output->writeln('[ERROR]服务启动错误：' . $e->getMessage());
        }
    }
}
